package com.qianfeng.smartplatform.websocket;

//
//                            _ooOoo_  
//                           o8888888o  
//                           88" . "88  
//                           (| -_- |)  
//                            O\ = /O  
//                        ____/`---'\____  
//                      .   ' \\| |// `.  
//                       / \\||| : |||// \  
//                     / _||||| -:- |||||- \  
//                       | | \\\ - /// | |  
//                     | \_| ''\---/'' | |  
//                      \ .-\__ `-` ___/-. /  
//                   ___`. .' /--.--\ `. . __  
//                ."" '< `.___\_<|>_/___.' >'"".  
//               | | : `- \`.;`\ _ /`;.`/ - ` : | |  
//                 \ \ `-. \_ __\ /__ _/ .-` / /  
//         ======`-.____`-.___\_____/___.-`____.-'======  
//                            `=---='  
//  
//         .............................................  
//                  佛祖镇楼                  BUG辟易  
//          佛曰:  
//                  写字楼里写字间，写字间里程序员；  
//                  程序人员写程序，又拿程序换酒钱。  
//                  酒醒只在网上坐，酒醉还来网下眠；  
//                  酒醉酒醒日复日，网上网下年复年。  
//                  但愿老死电脑间，不愿鞠躬老板前；  
//                  奔驰宝马贵者趣，公交自行程序员。  
//                  别人笑我忒疯癫，我笑自己命太贱；  
//  


import com.qianfeng.smartplatform.event.CheckOnlineEvent;
import com.qianfeng.smartplatform.event.DeviceOnLineEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.Map;

/**
 * Created by Jackiechan on 2022/5/31 10:26
 * 当前对象是单例对象
 *
 * @author Jackiechan
 * @version 1.0
 * @since 1.0
 */
@Component
@EnableAsync(proxyTargetClass = true)
public abstract class MyBaseWebSocketHandler extends TextWebSocketHandler {
    //保存心跳时间的map

    private ApplicationContext context;

    public ApplicationContext getContext() {
        return context;
    }

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }

    /**
     * 当客户端和我们建立连接之后执行
     *
     * @param session 正在建立的会话
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String uri = session.getUri().toString();//我们请求的什么地址建立的连接 ,比如/websocket/zhangsan
        //我们约定,最后一个/后的内容就是你是谁
        String deviceId = uri.substring(uri.lastIndexOf("/") + 1);
        session.getAttributes().put("name", deviceId);//把名字保存到了会话中
        //如果之前有会话应该先关闭再添加
        WebSocketSession currentSession = getAllClient().get(deviceId);
        //如果之前存在连接就先关闭
        if (currentSession != null && currentSession.isOpen()) {
            currentSession.close();
        }
        //保存起来
        getAllClient().put(deviceId, session);
        //首次建立连接就相当于收到了一次心跳
        getHeartbeatTime().put(deviceId, System.currentTimeMillis());
        System.err.println("MyDevicePowerHandler中的方法afterConnectionEstablished 执行了");
        if (isNeedUpdateOnline()) {
            //需要更新状态的就更新
            DeviceOnLineEvent deviceOnLineEvent = new DeviceOnLineEvent();
            deviceOnLineEvent.setDeviceId((String) deviceId);
            deviceOnLineEvent.setIp(session.getRemoteAddress().getHostString());
            //将事件发布出去
            context.publishEvent(deviceOnLineEvent);
        }
    }

    /**
     * 收到消息的时候怎么处理
     *
     * @param session 发送消息的会话对象
     * @param message 发送的消息
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.err.println("MyTextMessageHandler中的方法handleTextMessage 执行了===>" + message.getPayload());
        //记录一下发送的时间
        // 获取到当前发送消息的设备的id
        Object deviceId = session.getAttributes().get("name");
        getHeartbeatTime().put((String) deviceId, System.currentTimeMillis());
    }

    /**
     * 处理心跳方法
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
        super.handlePongMessage(session, message);
    }

    /**
     * 当客户端和我们关闭连接之后执行,正在关闭的会话
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Object deviceId = session.getAttributes().get("name");
        getAllClient().remove(deviceId);//移除了会话
        getHeartbeatTime().remove(deviceId);
        if (isNeedUpdateOnline()) {
            DeviceOnLineEvent deviceOnLineEvent = new DeviceOnLineEvent();
            deviceOnLineEvent.setDeviceId(deviceId.toString());
            //发布事件,设置为离线
            context.publishEvent(deviceOnLineEvent);
        }
    }

    @EventListener
    @Async
    public void onEvent(CheckOnlineEvent event) {
        System.err.println("MyBaseWebSocketHandler中的方法检查心跳 执行了");
        //遍历所有的
        getHeartbeatTime().entrySet().forEach(entry->{
            String deviceId = entry.getKey();//设备的id
            Long lastHeartTime = entry.getValue();//上次的心跳
            if (System.currentTimeMillis() - lastHeartTime >= 60000) {
                //超过了指定时间没有心跳,就关闭会话
                //从所有的客户端中找到当前的设备的会话
                WebSocketSession session = getAllClient().get(deviceId);
                if (session != null&&session.isOpen()) {
                    try {
                        //关闭会话
                        session.close();
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
    }


    /**
     * 钩子方法,用于判断是不是需要更新设备的在线状态
     * 钩子方法就是在父类中声明,父类中使用,但是由子类实现的方法
     * @return
     */
    public abstract boolean isNeedUpdateOnline();

    protected abstract  Map<String, Long> getHeartbeatTime();

    protected abstract  Map<String, WebSocketSession> getAllClient();

}
